
import { AlignType, ClassName, CoordinateSystem } from "./Constants";
import { IPoint, ISize, Itree, IVctor, NearNode } from "./Interface";

export namespace Utils {

    export function getVariance(arr: number[]) {
        let s,
            ave,
            sum = 0,
            sums = 0,
            len = arr.length;
        for (let i = 0; i < len; i++) {
            sum += Number(arr[i]);
        }
        ave = sum / len;
        for (let i = 0; i < len; i++) {
            sums += (Number(arr[i]) - ave) * (Number(arr[i]) - ave)
        }
        return sums / len;
    }

    // 求平均数
    export function getAverage(numbers: number[]) {
        if (numbers.length === 0) return 0; // 防止除以0
        const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue);
        const average = sum / numbers.length;
        return average;
    }
    /**
     * 获取鼠标点击后相对于canvas左上角的坐标
     * @param domElement 
     * @param e 
     * @returns 
     */
    export function getMousePos(domElement: Element, e: any | IPoint, cbOut = true): IPoint & { isoutX?: boolean, isoutY?: boolean } {
        let mouseX: number = 0;
        let mouseY: number = 0;

        if (e.clientX && e.clientY) {
            mouseX = e.clientX;
            mouseY = e.clientY;
        } else if (e.touches && e.touches.length > 0) {
            if (e.touches[0]) {
                mouseX = e.touches[0].clientX;
                mouseY = e.touches[0].clientY;
            } else if (e.changedTouches) {
                mouseX = e.changedTouches[0].clientX;
                mouseY = e.changedTouches[0].clientY;
            }
        }

        let { left, top, width, height } = domElement.getBoundingClientRect();

        let x = mouseX - left;
        let y = mouseY - top;
        let isoutX = false;
        let isoutY = false;

        if (x >= width) {
            isoutX = true;
        }
        if (x <= 0) {
            isoutX = true;
        }
        if (y >= height) {
            isoutY = true;
        }
        if (y <= 0) {
            isoutY = true;
        }

        return { x, y, isoutX, isoutY };
    }

    export function isMobileDevice() {
        // 这些正则表达式包含了常见的移动设备和浏览器的用户代理字符串  
        var mobileUserAgentPatterns = [
            /Android/i,
            /webOS/i,
            /iPhone/i,
            /iPad/i,
            /iPod/i,
            /BlackBerry/i,
            /Windows Phone/i,
            /Opera Mini/i,
            /IEMobile/i
        ];

        // 获取用户代理字符串  
        var userAgent = navigator.userAgent || navigator.vendor || window.opera;

        // 遍历正则表达式数组，检查用户代理字符串是否匹配其中任何一个  
        for (var i = 0; i < mobileUserAgentPatterns.length; i++) {
            if (userAgent.match(mobileUserAgentPatterns[i])) {
                return true; // 如果匹配，则返回true，表示是移动设备  
            }
        }

        return false; // 如果没有匹配项，则返回false，表示不是移动设备  
    }

    /**
     * 生成唯一id
     * @returns 
     */
    export function getUuid(): string {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0, v
                = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16);
        });
    }

    export function crossMul(p1: IPoint, p2: IPoint) {
        return p1.x * p2.y - p1.y * p2.x;
    }

    //  求两个向量之间的夹角
    export function getAngleOfTwoVct(vector1: IVctor, vector2: IVctor) {
        var angle = Math.atan2(vector2[1], vector2[0]) - Math.atan2(vector1[1], vector1[0]);
        // if (angle < 0) angle += 2 * Math.PI;
        return angle;
    }

    // 16进制转rgb
    export function hex2Rgba(hex: string, opacity: number) {
        if (!hex) hex = '#ededed';
        let rgba = 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' +
            parseInt('0x' + hex.slice(3, 5)) + ',' +
            parseInt('0x' + hex.slice(5, 7)) + ',' +
            (opacity || "1") + ')'
        return rgba
    }

    // rgba转16进制
    export function rgb2Hex(rgb: string) {
        if (!rgb) rgb = 'rgb(237,237,237)'
        var regexp = /[0-9]{0,3}/g
        var res: any = rgb.match(regexp) // 利用正则表达式去掉多余的部分，将rgb中的数字提取
        var hexRes = '#'
        var hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
        var hexArr = []
        for (let i = 0; i < res.length; i++) {
            if (res[i]) {
                if (res[i] > 16) {
                    let leftIndex = (res[i] / 16) >> 0 // 向下取整
                    let rightIndex = +res[i] % 16
                    hexArr.push(hex[leftIndex])
                    hexArr.push(hex[rightIndex])
                } else {
                    hexArr.push(0)
                    hexArr.push(hex[res[i]])
                }
            }
        }
        return hexRes += hexArr.join('')  // #EDEDED
    }


    /**
     * 获取随机数,指定范围内
     */
    export function randomNum(minNum: number, maxNum: number) {
        switch (arguments.length) {
            case 1:
                return Math.random() * minNum + 1;
            case 2:
                return Math.random() * (maxNum - minNum + 1) + minNum;
            default:
                return 0;
        }
    }

    // 根据两个点坐标创建向量
    export function getVector(start: IPoint, end: IPoint): IVctor {
        let x = end.x - start.x;
        let y = end.y - start.y;
        return [x, y];
    };
    // 获取向量的长度
    export function getVctLen(vct: IVctor): number {
        return Math.sqrt(vct[0] * vct[0] + vct[1] * vct[1]);
    };

    //   向量加法
    export function vctAdd(vct1: IVctor, vct2: IVctor): IVctor {
        return [vct1[0] + vct2[0], vct1[1] + vct2[1]];
    };

    /**
    * 根据点在向量上的比例计算点坐标, [xO, yO]为起点，[xVct, yVct]为向量，k 为该点在向量方向上的长度
    * 获取
    */
    export function getPntInVct(O: IPoint, vct: IVctor, k = 0): IPoint {
        let lenVct = getVctLen(vct);  // 获取向量长度
        let stdVct;
        if (lenVct === 0) {
            stdVct = [0, 0];
        } else {
            stdVct = [vct[0] / lenVct, vct[1] / lenVct];   // 单位向量
        }
        // return [O.x + k * stdVct[0], O.y + k * stdVct[1]];
        return {
            x: O.x + k * stdVct[0],
            y: O.y + k * stdVct[1],
        };
    };

    // 向量的叉乘可以判断旋转角度
    // 向量的点乘可以表示投影
    export function getRotateType(vct1: IVctor, vct2: IVctor) {
        let cross = vct1[0] * vct2[1] - vct1[1] * vct2[0];  // 通过向量的叉乘判断顺时针还是逆时针
        if (cross < 0) {
            return '>180';
        } else {
            return '<=180';
        }
    };

    export function getRotateAng(vct: IVctor = [0, 0], vct2: IVctor = [0, 0]) {
        let EPSILON = 1.0e-8;
        let dist, dot, cross, degree, angle;

        dist = Math.sqrt(vct[0] * vct[0] + vct[1] * vct[1]);
        vct[0] /= dist;
        vct[1] /= dist;
        dist = Math.sqrt(vct2[0] * vct2[0] + vct2[1] * vct2[1]);
        vct2[0] /= dist;
        vct2[1] /= dist;

        dot = vct[0] * vct2[0] + vct[1] * vct2[1];
        if (Math.abs(dot - 1.0) <= EPSILON) {
            angle = 0;
        } else if (Math.abs(dot + 1.0) <= EPSILON) {
            angle = Math.PI;
        } else {
            angle = Math.acos(dot);
            cross = vct[0] * vct2[1] - vct2[0] * vct[1];
            if (cross < 0) {
                angle = 2 * Math.PI - angle;
            }
        }
        degree = angle * 180 / Math.PI;
        return degree;
    };

    // 获取两个点的距离
    export function getLenOfTwoPnts(p1: IPoint, p2: IPoint) {
        return Math.sqrt(Math.pow((p1.x - p2.x), 2) + Math.pow((p1.y - p2.y), 2));
    };
    // 获取两个点的中点
    export function getMidOfTwoPnts(p1: IPoint, p2: IPoint) {
        return {
            x: (p1.x + p2.x) / 2,
            y: (p1.y + p2.y) / 2
        }
    };

    /**
     * 获取O点到直线PQ的垂直距离
     */
    export function getLenOfPntToLine(O: IPoint, P: IPoint, Q: IPoint, absolute = true) {
        if ((O.x == P.x && O.y == P.y) || (P.x == Q.x && P.y == Q.y)) {
            return 0;
        }
        let rotateAng = getRotateAng([O.x - P.x, O.y - P.y], [Q.x - P.x, Q.y - P.y]);
        let len = getLenOfTwoPnts(O, P) * Math.sin(rotateAng * Math.PI / 180);
        return absolute ? Math.abs(len) : len;  // 不要打开方向有问题
    };

    export function isPntLeftOrRightOfLine(O: IPoint, P: IPoint, Q: IPoint) {
        // 计算有向距离  
        const crossProduct = (Q.x - P.x) * (O.y - P.y) - (Q.y - P.y) * (O.x - P.x);

        // 如果叉积为正，则点在直线的左侧  
        // 如果叉积为负，则点在直线的右侧  
        // 如果叉积为零，则点在直线上  
        if (crossProduct > 0) {
            return "left";
        } else if (crossProduct < 0) {
            return "right";
        } else {
            return "on";
        }
    }

    export function getShadowPnt(O: IPoint, P: IPoint, Q: IPoint) {
        let vctQO = getVector(Q, O);
        let vctQP = getVector(Q, P);
        if (vctQO[0] === 0 && vctQO[1] === 0) {
            return Q;
        }
        let angle = getRotateAng(vctQP, vctQO);
        let QH = getLenOfTwoPnts(Q, O) * Math.cos(angle * Math.PI / 180);
        let H = getPntInVct(Q, vctQP, QH);
        return H;
    };

    // 向量旋转angle角度得到新的向量
    export function getRotateVct(vct: IVctor, angle = 0): IVctor {
        let rad = angle * Math.PI / 180;
        let x1 = Math.cos(rad) * vct[0] - Math.sin(rad) * vct[1];
        let y1 = Math.sin(rad) * vct[0] + Math.cos(rad) * vct[1];
        return [x1, y1];  // 返回的是向量
    };

    // 向量旋转angle角度得到新的向量
    export function getRevertVct(vct: IVctor): IVctor {
        return [vct[0] * -1, vct[1] * -1];  // 返回的是向量
    };

    /**
    * P 绕 O 点逆时针旋转angle角度后得到新的点坐标
    */
    export function getRotatePnt(O: IPoint, P: IPoint, angle: number): IPoint {
        if (angle === 0) {
            return P;
        }
        let vctOP = getVector(O, P),
            OP = getVctLen(vctOP);
        let dvctOQ = getRotateVct(vctOP, angle);
        let newPoint = getPntInVct(O, dvctOQ, OP);
        return newPoint;
    };

    /**
    * 在一段三次贝塞尔曲线上均匀取点, 不包括终点
    * @param counts 一段贝塞尔曲线中取点的数量
    */

    export function getPntsOf3Bezier(p0: IPoint, cp1: IPoint, cp2: IPoint, p3: IPoint, counts: number): IPoint[] {
        let per = counts && counts != 0 ? 1 / counts : 0.02;    //取点间隔
        let points: IPoint[] = [];
        for (let t = 0; t <= 1; t += per) {
            points.push(getPntIn3Bezier(p0, cp1, cp2, p3, t));
        }
        return points;
    };

    /**
     * 获取三次贝塞尔曲线上的一点，t∈[0,1]
     * @param t 介于0 ~ 1, 表示点在曲线中的相对位置
     */
    export function getPntIn3Bezier(p0: IPoint, cp1: IPoint, cp2: IPoint, p3: IPoint, t: number) {
        // 确保t在0到1之间  
        t = Math.max(0, Math.min(1, t));

        // 三次贝塞尔曲线的公式  
        var oneMinusT = 1 - t;
        var oneMinusTSquared = Math.pow(oneMinusT, 2);
        var oneMinusTCubed = Math.pow(oneMinusT, 3);
        var tSquared = Math.pow(t, 2);
        var tCubed = Math.pow(t, 3);

        // 计算x和y坐标  
        var x = oneMinusTCubed * p0.x + 3 * oneMinusTSquared * t * cp1.x + 3 * oneMinusT * tSquared * cp2.x + tCubed * p3.x;
        var y = oneMinusTCubed * p0.y + 3 * oneMinusTSquared * t * cp1.y + 3 * oneMinusT * tSquared * cp2.y + tCubed * p3.y;

        // 返回曲线上的点  
        return { x: x, y: y };
    }

    export function getPntsOf2Bezier(p1: IPoint, cp: IPoint, p2: IPoint, counts: number): IPoint[] {
        let per = counts && counts != 0 ? 1 / counts : 0.02;    //取点间隔
        let points: IPoint[] = [];
        for (let t = 0; t <= 0.999999; t += per) {
            points.push(getPntIn2Bezier(p1, cp, p2, t));
        }
        return points;
    };


    function getPntIn2Bezier(p1: IPoint, cp: IPoint, p2: IPoint, t: number) {
        // 确保t在0到1之间  
        t = Math.max(0, Math.min(1, t));

        // 二次贝塞尔曲线的公式  
        var oneMinusT = 1 - t;
        var oneMinusTSquared = Math.pow(oneMinusT, 2);
        var tSquared = Math.pow(t, 2);

        // 计算x和y坐标  
        var x = oneMinusTSquared * p1.x + 2 * oneMinusT * t * cp.x + tSquared * p2.x;
        var y = oneMinusTSquared * p1.y + 2 * oneMinusT * t * cp.y + tSquared * p2.y;

        // 返回曲线上的点  
        return { x: x, y: y };
    }


    // 获取选段百分之多少处的点 per: 0~1
    export function getPointOfPer(per: number, pointArr = []) {
        const index = Math.round(pointArr.length * per);
        return pointArr[index]
    }

    // 每两点之间线性插值
    export function getInterpolatePnts(points: IPoint[] = [], closeEnd = false, d = 1) {
        const pointArr = points.slice();
        for (let i = pointArr.length - 1; i > -1; i--) {
            if (i < pointArr.length - 1) {
                const generateArr = Utils.generatePntsOfTwoPnt(pointArr[i], pointArr[i + 1], d)
                pointArr.splice(i + 1, 0, ...generateArr)
            }
        }
        if (closeEnd) {
            pointArr.push(...Utils.generatePntsOfTwoPnt(pointArr[0], pointArr[pointArr.length - 1]).reverse());  // 最后一段
        }
        return pointArr;
    }

    /**
     * 在一段椭圆弧上均匀取点，rotateAngle是椭圆绕中心点逆时针旋转的角度
     * endAngle必须大于startAngle, ~[-360, 360]
     * @param xiaoshu bool, 角度是否为整数, true则添加最后一个点
     */
    export function getPntsInEllipse(center: IVctor, majorRadius: number, minorRadius: number, startAngle: number, endAngle: number, rotateAngle: number, xiaoshu?: boolean) {
        let x = null,
            y = null,
            points = [],
            perAngle = Math.PI / 180,
            startAngle_ = startAngle * Math.PI / 180,
            endAngle_ = endAngle * Math.PI / 180;
        let angle = startAngle_;

        if (rotateAngle === 0 || !rotateAngle) {
            for (let i = 0; i <= (endAngle_ - startAngle_) / perAngle; i++) {
                x = center[0] + majorRadius * Math.cos(angle);
                y = center[1] + minorRadius * Math.sin(angle);
                points.push({ x, y });
                angle += perAngle;
            }
            if (xiaoshu) {
                points.push({ x: center[0] + majorRadius * Math.cos(endAngle_), y: center[1] + minorRadius * Math.sin(endAngle_) })
            }
        } else {
            for (let i = 0; i <= (endAngle_ - startAngle_) / perAngle; i++) {
                x = center[0] + majorRadius * Math.cos(angle);
                y = center[1] + minorRadius * Math.sin(angle);
                let temp = getRotatePnt({ x: center[0], y: center[1] }, { x, y }, rotateAngle);
                points.push(temp);
                angle += perAngle;
            }
            if (xiaoshu) {
                points.push({ x: center[0] + majorRadius * Math.cos(endAngle_), y: center[1] + minorRadius * Math.sin(endAngle_) })
            }
        }
        return points;
    };

    // 判断点是否在多边形内
    export function isPntInPolygon(p: IPoint, polygon: IPoint[]) {
        var j = polygon.length - 1;
        var isInside = false;
        for (var i = 0; i < polygon.length; i++) {
            if (
                ((polygon[i].y < p.y && polygon[j].y >= p.y) || (polygon[j].y < p.y && polygon[i].y >= p.y))
                &&
                (polygon[i].x + (p.y - polygon[i].y) / (polygon[j].y - polygon[i].y) * (polygon[j].x - polygon[i].x) < p.x)
            ) {
                isInside = !isInside;
            }
            j = i;
        }
        return isInside;
    }

    export function isPointInCircle(x = 0, y = 0, centerX = 0, centerY = 0, radius = 10) {
        // 计算点到圆心的水平距离和垂直距离  
        let dx = x - centerX;
        let dy = y - centerY;
        // 使用勾股定理计算点到圆心的距离  
        let distance = Math.sqrt(dx * dx + dy * dy);
        // 判断距离是否小于等于半径  
        return distance <= radius;
    }

    export function getRectPoints(pos: IPoint, size: ISize) {
        return [
            { x: pos.x - size.width / 2, y: pos.y - size.height / 2 },
            { x: pos.x + size.width / 2, y: pos.y - size.height / 2 },
            { x: pos.x + size.width / 2, y: pos.y + size.height / 2 },
            { x: pos.x - size.width / 2, y: pos.y + size.height / 2 },
        ]
    }

    export function getBorderOfRect(pos: IPoint, size: ISize, border: AlignType | AlignType[]) {
        let borderPnts = []
        switch (border) {
            case AlignType.TOP:
                borderPnts.push({ x: pos.x - size.width / 2, y: pos.y - size.height / 2 }, { x: pos.x + size.width / 2, y: pos.y - size.height / 2 })
                break;
            case AlignType.RIGHT:
                borderPnts.push({ x: pos.x + size.width / 2, y: pos.y - size.height / 2 }, { x: pos.x + size.width / 2, y: pos.y + size.height / 2 })
                break;
            case AlignType.BOTTOM:
                borderPnts.push({ x: pos.x + size.width / 2, y: pos.y + size.height / 2 }, { x: pos.x - size.width / 2, y: pos.y + size.height / 2 })
                break;
            case AlignType.LEFT:
                borderPnts.push({ x: pos.x - size.width / 2, y: pos.y + size.height / 2 }, { x: pos.x - size.width / 2, y: pos.y - size.height / 2 })
                break;
            default:
                break;
        }
        return borderPnts;
    }

    export function getRoundRectPoints(pos: IPoint, size: ISize, radius = 0, directions = [AlignType.LEFT_TOP, AlignType.LEFT_BOTTOM, AlignType.RIGHT_BOTTOM, AlignType.RIGHT_TOP], genratorNum = 10) {
        if (radius == 0) {
            return getRectPoints(pos, size)
        }
        const topPoint = [{ x: pos.x - size.width / 2 + radius, y: pos.y - size.height / 2 }, { x: pos.x + size.width / 2 - radius, y: pos.y - size.height / 2 }]
        const rightPoint = [{ x: pos.x + size.width / 2, y: pos.y - size.height / 2 + radius }, { x: pos.x + size.width / 2, y: pos.y + size.height / 2 - radius }]
        const bottomPoint = [{ x: pos.x + size.width / 2 - radius, y: pos.y + size.height / 2 }, { x: pos.x - size.width / 2 + radius, y: pos.y + size.height / 2 }]
        const leftPoint = [{ x: pos.x - size.width / 2, y: pos.y + size.height / 2 - radius }, { x: pos.x - size.width / 2, y: pos.y - size.height / 2 + radius }]

        const leftTopPoint = { x: pos.x - size.width / 2, y: pos.y - size.height / 2 }
        const rightTopPoint = { x: pos.x + size.width / 2, y: pos.y - size.height / 2 }
        const rightBottomPoint = { x: pos.x + size.width / 2, y: pos.y + size.height / 2 }
        const leftBottom = { x: pos.x - size.width / 2, y: pos.y + size.height / 2 }

        const arcPoint1 = directions.includes(AlignType.RIGHT_TOP) ? getPntsOf2Bezier(topPoint[1], rightTopPoint, rightPoint[0], genratorNum) : [rightTopPoint];
        const arcPoint2 = directions.includes(AlignType.RIGHT_BOTTOM) ? getPntsOf2Bezier(rightPoint[1], rightBottomPoint, bottomPoint[0], genratorNum) : [rightBottomPoint];
        const arcPoint3 = directions.includes(AlignType.LEFT_BOTTOM) ? getPntsOf2Bezier(bottomPoint[1], leftBottom, leftPoint[0], genratorNum) : [leftBottom];
        const arcPoint4 = directions.includes(AlignType.LEFT_TOP) ? getPntsOf2Bezier(leftPoint[1], leftTopPoint, topPoint[0], genratorNum) : [leftTopPoint];
        return [
            ...topPoint,
            ...arcPoint1,
            ...rightPoint,
            ...arcPoint2,
            ...bottomPoint,
            ...arcPoint3,
            ...leftPoint,
            ...arcPoint4,
        ]
    }

    // 数组两个元素交换位置
    export function swapElements<F>(arr: F[], index1 = 0, index2 = 0) {
        if (index1 < 0 || index1 >= arr.length || index2 < 0 || index2 >= arr.length) {
            console.log("Invalid index");
            return;
        }
        var temp = arr[index1];
        arr[index1] = arr[index2];
        arr[index2] = temp;
    }

    export function isBase64(str: string) {
        try {
            const decodedStr = atob(str);
            // 如果解码成功，再判断解码后的字符串是否等于原始字符串
            return btoa(decodedStr) === str;
        } catch (error) {
            return false; // 解码失败则认为不是Base64
        }
    }

    // 获取字符串大小
    export function getSizeInBytes(str: string) {
        var binaryString = window.atob(str); // 将Base64字符串转换为二进制字符串
        return binaryString.length; // 返回二进制字符串的长度（单位为字节）
    }

    export function compressImage(src: string, quality = .7): Promise<string> {
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.src = src;
            img.onload = () => {
                // 创建 canvas 元素  
                const ratio = img.width / img.height;   // 原图片宽高比
                const size = img.width * quality;
                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;

                // 设置 canvas 的尺寸，这里可以根据目标宽度按比例缩放  
                canvas.width = size;
                canvas.height = size / ratio; // 保持宽高比

                // 绘制图片到 canvas  
                ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
                const newSrc = canvas.toDataURL('image/png', 1);
                resolve(newSrc)
            }
        })
    }

    export function selectFiles(accept = ''): Promise<FileList> {
        return new Promise((resolve, reject) => {
            const input = document.createElement('input');
            input.type = "file";
            input.accept = accept;
            input.click();
            input.onchange = () => {
                if (input.files && input.files.length > 0) {
                    resolve(input.files)
                } else {
                    throw new Error("请选择至少一个文件!");
                }
            }
        })
    }

    export function downloadFile(data: BlobPart[], options?: BlobPropertyBag, filename?: string) {
        const blob = new Blob(data, {
            ...options,
        })
        // 根据 blob生成 url链接
        const objectURL = URL.createObjectURL(blob)
        const domElement = document.createElement('a')
        domElement.href = objectURL;
        domElement.download = filename || `${new Date().toLocaleDateString() + " " + new Date().toLocaleTimeString()}`
        domElement.click()
        URL.revokeObjectURL(objectURL)
    }

    export function base64ToBlob(dataurl: string) {
        let arr = dataurl.split(',')
        let mime = arr[0].match(/:(.*?);/)[1]
        let bstr = atob(arr[1])
        let n = bstr.length
        let u8arr = new Uint8Array(n)
        while (n--) {
            u8arr[n] = bstr.charCodeAt(n)
        }
        return new Blob([u8arr], { type: mime })
    }

    export function clipboardText(text: string) {
        const textarea = document.createElement("textarea");
        document.body.appendChild(textarea);
        textarea.value = text;
        textarea.select();
        document.execCommand("copy")
        document.body.removeChild(textarea);
    }

    export function recorderCanvas(canvas: HTMLCanvasElement) {
        const stream = canvas.captureStream() // 获取stream
        const recorder = new MediaRecorder(stream, {
        })
        const datas: any[] = [] // 用来记录数据
        recorder.ondataavailable = function (event) {
            if (event.data && event.data.size) {
                datas.push(event.data)
            }
        }
        recorder.onstop = function () {
            const url = URL.createObjectURL(new Blob(datas, {
                type: 'video/mp4'
            }));
            const domElement = document.createElement("a");
            domElement.href = url;
            domElement.download = `${new Date().toLocaleDateString() + " " + new Date().toLocaleTimeString()
                }.mp4`;
            domElement.click();
            URL.revokeObjectURL(url);
        }
        recorder.start()
        return recorder;
    }

    export function compressVideo(src: string, quality = .7): Promise<string> {
        return new Promise((resolve, reject) => {
            const video = document.createElement("video");
            video.src = src;
            video.muted = true;
            video.addEventListener('loadeddata', () => {
                video.play();
                const ratio = video.videoWidth / video.videoHeight;
                const size = video.videoWidth * quality;
                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
                canvas.width = size;
                canvas.height = size / ratio;
                (function draw() {
                    ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
                    requestAnimationFrame(draw);
                })()
                const recorder = recorderCanvas(canvas);
                video.onended = () => {
                    recorder.stop();
                }
                resolve('')
            });
        })
    }

    // ----------------------------判断型方法-------------------------------
    // 判断是否时基础元素
    export function isBasicFeature(f?: any, hasGroup = true, hasLink = true, hasArrow = false) {  //  hasGroup 是否包括Group类元素
        if (!f) return false;
        if (!hasGroup && f.className === ClassName.GROUP) return
        if (!hasLink && f.className === ClassName.LINK) return
        if (!hasArrow && f.className === ClassName.ARROW) return
        return f.className == ClassName.IMG || f.className == ClassName.LINE || f.className == ClassName.RECT || f.className == ClassName.TEXT || f.className == ClassName.CIRCLE || f.className == ClassName.GROUP || f.className == ClassName.VIDEO || f.className === ClassName.PEN || f.className === ClassName.LINK || f.className === ClassName.FEATURE
    }

    export function isAsyncFeature(f?: any) {  //  hasGroup 是否包括Group类元素
        if (!f) return false;
        return f.className == ClassName.IMG || f.className == ClassName.VIDEO
    }

    export function getAngleOfTwoPnts(p1: IPoint, p2: IPoint) {
        var deltaX = p2.x - p1.x;
        var deltaY = p2.y - p1.y;
        return Math.atan2(deltaY, deltaX) * 180 / Math.PI;
    }

    export function getUnitSize() {
        return CoordinateSystem.GRID_SIZE * CoordinateSystem.GRID_SIZE
    }

    // 判断一个点在另一个点的哪个方位
    export function determinePosition(pointA: IPoint, pointB: IPoint) {
        const dy = pointB.y - pointA.y;
        const dx = pointB.x - pointA.x;
        if (Math.abs(dx) > Math.abs(dy)) {
            if (dx > 0) {
                return AlignType.RIGHT;
            } else {
                return AlignType.LEFT;
            }
        } else {
            if (dy > 0) {
                return AlignType.BOTTOM;
            } else {
                return AlignType.TOP;
            }
        }
    }

    // 离散点是否趋近于一条直线
    export function getLinearRegression(points: IPoint[]) {
        // 斜率：描述直线倾斜程度的量，对于直线 y=mx+b，斜率 m 决定了直线的方向
        // 截距：直线与坐标轴交点的坐标值。y-截距是直线与y轴交点的y坐标，x-截距是直线与x轴交点的x坐标
        let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
        let n = points.length;

        for (let i = 0; i < n; i++) {
            sumX += points[i].x;
            sumY += points[i].y;
            sumXY += points[i].x * points[i].y;
            sumX2 += points[i].x * points[i].x;
        }

        let m = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
        let b = (sumY - m * sumX) / n;

        return { m, b };
    }

    // 已知一个点和斜率,求线段上的另一个点  使用点斜式方程计算 y2  
    export function getPntOnLine(x1: number, y1: number, m: number, x2: number) {
        let y2 = y1 + m * (x2 - x1);
        return { x: x2, y: y2 };
    }

    // // 绘制圆角矩形
    // export  function getRoundedRect(x: number, y: number, w: number, h: number, r: number) {
    //     const path = new Path2D();
    //     path.moveTo(x + r, y);
    //     path.lineTo(x + w / 4 - r, y);
    //     path.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2);
    //     path.lineTo(x + w, y + h - r);
    //     path.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5);
    //     path.lineTo(x + r, y + h);
    //     path.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI);
    //     path.lineTo(x, y + r);
    //     path.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
    //     return path;
    // }

    export function getCirclePoints(x: number, y: number, width: number, height: number, angle = 0) {
        let O: [number, number] = [x, y];
        let points = getPntsInEllipse(O, width / 2, height / 2, 0, 360, angle);
        return points;
    }

    export function getNearestPoint(target: IPoint, points: IPoint[]) {
        let nearestPoint = null;
        let minDistance = Infinity;
        for (let i = 0; i < points.length; i++) {
            const point = points[i];
            // 跳过目标点自身  
            if (point.x === target.x && point.y === target.y || point === target) {
                continue;
            }
            // 计算当前点到目标点的距离  
            const distance = Math.sqrt(Math.pow(point.x - target.x, 2) + Math.pow(point.y - target.y, 2));
            // 如果当前点距离更近，则更新最近点和最小距离  
            if (distance < minDistance) {
                nearestPoint = point;
                minDistance = distance;
            }
        }
        return nearestPoint;
    }

    // 获取当前节点附近的6个点,分别是 左, 上, 右, 下, 左上, 右上, 右下, 左下
    export function getNearNodes(startPos: IPoint, endPos: IPoint, unitLen = 1) {
        let leftNode = {
            name: "left",
            x: startPos.x - unitLen,
            y: startPos.y,
            g: 1,
            h: 0,
            f: 0,
        };
        setDistProp(leftNode);

        let topNode = {
            name: "top",
            x: startPos.x,
            y: startPos.y - unitLen,
            g: 1,
            h: 0,
            f: 0,
        };
        setDistProp(topNode);


        let rightNode = {
            name: "right",
            x: startPos.x + unitLen,
            y: startPos.y,
            g: 1,
            h: 0,
            f: 0,
        };
        setDistProp(rightNode);

        let bottomNode = {
            name: "bottom",
            x: startPos.x,
            y: startPos.y + unitLen,
            g: 1,
            h: 0,
            f: 0,
        };
        setDistProp(bottomNode);

        return [leftNode, topNode, rightNode, bottomNode,]

        function setDistProp(startPos: NearNode) {
            if (!startPos.g) {
                startPos.g = 0;
            }
            startPos.h = Math.abs(endPos.x - startPos.x) + Math.abs(endPos.y - startPos.y);
            startPos.f = startPos.g + startPos.h;
        }
    }

    export function getTreeLayout(node: Itree, x = 0, y = 0, paddingY = 50, paddingX = 150) {

        if (!node.children) {
            node.children = []
        }

        let bool = false;

        // 计算当前节点的起始x坐标，使其居中对齐  
        node.x = x;
        node.y = y;

        const childY = y + paddingY; // 子节点的y坐标  
        const childX = x; // 子节点的起始x坐标与当前节点相同（为了居中对齐）  

        // 遍历子节点  
        if (node.children.length % 2 === 0) {  // 子节点是偶数,中间空着
            node.children.forEach((cd, i) => {
                if (bool) {
                    cd.x = childX + paddingX
                } else {
                    cd.x = childX - paddingX
                }
                if (i % 2 === 0) {
                    paddingX * 2
                }
                bool = !bool
                getTreeLayout(cd, cd.x, childY, paddingY, paddingX);
            })
        } else {
            node.children.forEach((cd, i) => {
                if (i > 0) {
                    if (bool) {
                        cd.x = childX + paddingX
                    } else {
                        cd.x = childX - paddingX
                    }
                    if (i % 2 === 0) {
                        paddingX * 2
                    }
                    bool = !bool
                } else {
                    cd.x = childX
                }
                getTreeLayout(cd, cd.x, childY, paddingY, paddingX);
            })
        }

        return node;
    }

    // d 表示最小距离,越小生成的点越多
    export function generatePntsOfTwoPnt(p1: IPoint, p2: IPoint, d = 5) {
        const len = getLenOfTwoPnts(p1, p2);
        if (len < d) return [p1, p2];
        const times = Math.floor(len / d);
        const pointArr = []
        for (let index = 0; index < times; index++) {
            pointArr.push(getPointOnLine(p1, p2, index / times))
        }
        return pointArr;
    }

    // 获取线段上百分之多少的位置上的一个点
    export function getPointOnLine(p1: IPoint, p2: IPoint, t = .5) {
        // 确保t在0到1之间  
        t = Math.max(0, Math.min(1, t));
        const x = p1.x + t * (p2.x - p1.x);
        const y = p1.y + t * (p2.y - p1.y);
        return { x, y };
    }

    // 线段连接点变为圆角连接
    export function toRoundJoinLine(points: IPoint[], radius = .1) {
        const pointArr: IPoint[] = [];
        points.forEach((curP, i) => {
            if (i === 0 || i === points.length - 1) {
                pointArr.push(curP);
                return;
            }
            const prevP = points[i - 1];
            const nextP = points[i + 1];
            const prevCurPerP = getPointOnLine(prevP, curP, 1 - radius);
            const curNextPerP = getPointOnLine(curP, nextP, 0 + radius);
            const curvePnts = getPntsOf2Bezier(prevCurPerP, curP, curNextPerP, 10);
            pointArr.push(...curvePnts);
        })
        return pointArr;
    }

    // 函数节流
    export function throttle(func: Function, delay = 500) {
        let lastCall = 0;
        return function (...args) {
            const now = new Date().getTime();
            if (now - lastCall < delay) {
                return; // 如果距离上次调用时间小于 delay，则不执行  
            }
            lastCall = now;
            return func.apply(this, args); // 否则，执行函数，并更新 lastCall  
        };
    }

    // 函数防抖
    export function debounce(func: Function, delay = 500) {
        let timeout = 0;
        return function (...args) {
            linearRegression
            const context = this;
            clearTimeout(timeout); // 清除上一次设置的定时器  
            timeout = setTimeout(() => func.apply(context, args), delay); // 设置新的定时器  
        };
    }

    export function getDeriction(angle: number) {
        if (angle > 45 && angle <= 135) {
            return AlignType.RIGHT
        } else if (angle > 135 && angle <= 225) {
            return AlignType.BOTTOM
        } else if (angle > -45 && angle <= 45) {
            return AlignType.TOP
        } else {
            return AlignType.LEFT
        }
    }

    export function getDeriction2(angle: number) {
        if (angle > -90 && angle <= 0) {
            return AlignType.LEFT_TOP
        } else if (angle > 0 && angle <= 90) {
            return AlignType.RIGHT_TOP
        } else if (angle > 90 && angle <= 180) {
            return AlignType.RIGHT_BOTTOM
        } else {
            return AlignType.LEFT_BOTTOM
        }
    }

    export function getMidTwoOfPoints(points: IPoint[]): [IPoint, IPoint] {
        // 确保数组至少有2个点  
        if (points.length < 2) {
            throw new Error("数组中的点数不足");
        }

        // 获取数组长度  
        let length = points.length;

        // 计算中间两个点的索引  
        let midIndex1, midIndex2;
        if (length % 2 === 0) {
            // 如果长度是偶数，则中间两个点是中间两个连续的索引  
            midIndex1 = Math.floor(length / 2) - 1;
            midIndex2 = Math.floor(length / 2);
        } else {
            // 如果长度是奇数，则中间两个点是中间索引和它后面的一个  
            midIndex1 = Math.floor(length / 2);
            midIndex2 = midIndex1 + 1;
        }

        // 确保索引在数组范围内（针对奇数长度的情况，如果数组只有一个元素，上面的计算会导致索引超出范围）  
        midIndex1 = Math.max(0, midIndex1);
        midIndex2 = Math.min(length - 1, midIndex2);

        // 返回中间两个点  
        return [points[midIndex1], points[midIndex2]];
    }

    // 每两点插入一个中点
    export function insertMidpoints(pointArr: IPoint[]) {
        // 结果数组，用来存放插入中点后的坐标  
        const newPointArr = [];
        // 遍历坐标数组  
        for (let i = 0; i < pointArr.length - 1; i++) {
            // 获取当前点和下一点  
            const current = pointArr[i];
            const next = pointArr[i + 1];
            // 计算中间点  
            const midpoint = {
                x: (current.x + next.x) / 2,
                y: (current.y + next.y) / 2
            };
            // 将当前点和中点加入结果数组  
            newPointArr.push(current);
            newPointArr.push(midpoint);
        }
        // 最后一个点不需要再计算中点，直接添加到结果数组  
        newPointArr.push(pointArr[pointArr.length - 1]);
        return newPointArr;
    }


    /**
     * (平滑曲线)获得每个途经点的控制点信息, indicatorForcurve是曲线的弯曲程度指标(一般0.45)
     */
    export function getCtrlNodes(pointArr: IPoint[], indicatorForcurve: number) {
        let P1;
        let Ai;
        let Bi;
        let ctrlNodes = [];
        let len = pointArr.length;

        for (let i = 0; i < len; i++) {
            P1 = pointArr[i];
            if (i === 0) {
                if (len < 3) {
                    Ai = P1;
                    Bi = P1;
                } else {
                    let B1 = getCNode(pointArr[0], pointArr[1], pointArr[2], indicatorForcurve).Bi;
                    Ai = getStartCNode(pointArr[0], pointArr[1], B1);
                    Bi = P1;
                }
            } else if (i === len - 1) {
                if (len < 3) {
                    Ai = P1;
                    Bi = P1;
                } else {
                    let Alen_1 = getCNode(pointArr[len - 3], pointArr[len - 2], pointArr[len - 1], indicatorForcurve).Ai;
                    Ai = P1;
                    Bi = getEndCNode(pointArr[len - 1], pointArr[len - 2], Alen_1);
                }
            } else {
                let cNode = getCNode(pointArr[i - 1], P1, pointArr[i + 1], indicatorForcurve);
                Ai = cNode.Ai;
                Bi = cNode.Bi;
            }

            ctrlNodes.push({
                P1: P1,
                Ai: Ai,
                Bi: Bi
            })
        }

        return ctrlNodes;
    };

    /**
     * (平滑曲线)获得点 P1 处的两个控制点Ai, Bi
     */
    export function getCNode(P0: IPoint, P1: IPoint, P2: IPoint, indicatorForCurve: number) {
        let dvct = getTengent(P0, P1, P2),
            dvctAi = dvct.dvctAi,
            dvctBi = dvct.dvctBi,
            nearbyP = getNearbyPercent(P0, P1, P2),
            nearbyPAi = nearbyP.PAi,
            nearbyPBi = nearbyP.PBi,
            P0P1 = getLenOfTwoPnts(P0, P1),
            P1P2 = getLenOfTwoPnts(P1, P2);

        let PAi = nearbyPAi * indicatorForCurve * (P0P1 + P1P2),
            PBi = nearbyPBi * indicatorForCurve * (P1P2 + P0P1),
            Ai = getPntInVct(P1, dvctAi, PAi),
            Bi = getPntInVct(P1, dvctBi, PBi);
        return {
            Ai: Ai,
            Bi: Bi
        }
    };

    /**
     * (平滑曲线)计算附近对控制点生成a, b取值的影响比例
     */
    export function getNearbyPercent(p1: IPoint, p2: IPoint, p3: IPoint) {
        //用边长比
        let lenAB = getLenOfTwoPnts(p1, p2);
        let lenBC = getLenOfTwoPnts(p2, p3);
        let sidePc = {
            PAi: lenBC / (lenAB + lenBC),
            PBi: lenAB / (lenAB + lenBC)
        };

        let PAi = Math.pow(sidePc.PAi / 2, 0.6);
        let PBi = Math.pow(sidePc.PBi / 2, 0.6);
        return {
            PAi: PAi,
            PBi: PBi
        };
    };

    /**
     * (平滑曲线)计算某绘制点附近的平滑切线向量，P点处
     */
    export function getTengent(p1: IPoint, p2: IPoint, p3: IPoint): any {
        let lenPB = getLenOfTwoPnts(p3, p2),
            A1 = getPntInVct(p2, getVector(p2, p1), lenPB);
        let vctPA1 = getVector(p2, A1),
            vctPB = getVector(p2, p3),
            vctPC = vctAdd(vctPA1, vctPB);
        let vctPM, vctPM1;

        //旋转角小于180是顺时针曲线，超过180是逆时针曲线，两种情况下M、M1点的取值方向是相反的
        let rotateType = getRotateType(getVector(p2, p1), getVector(p2, p3));
        if (rotateType === '<=180') {
            vctPM = [-vctPC[1], vctPC[0]];
            vctPM1 = [-vctPM[0], -vctPM[1]];
        } else {
            vctPM = [vctPC[1], -vctPC[0]];
            vctPM1 = [-vctPM[0], -vctPM[1]];
        }

        return {
            dvctAi: vctPM,   // 两个方向上的向量
            dvctBi: vctPM1
        };
    };

    /**
     * (平滑曲线)获取起始点的控制点 Ai
     */
    export function getStartCNode(A: IPoint, B: IPoint, P: IPoint) {
        if (B.x === P.x && B.y === P.y) {
            return A;
        }
        let vctBA = getVector(B, A);
        let vctAB = getVector(A, B);
        let vctBP = getVector(B, P);
        let rAngle = getRotateAng(vctBA, vctBP);
        rAngle = rAngle > 180 ? rAngle - 360 : rAngle;
        let vctAQ = getRotateVct(vctAB, -rAngle * 0.5);
        let Q = getPntInVct(A, vctAQ, getVctLen(vctBP) * 0.6);
        return Q;
    };

    /**
     * (平滑曲线)获取终止点的控制点 Bi
     */
    export function getEndCNode(C: IPoint, B: IPoint, P: IPoint) {
        if (B.x === P.x && B.y === P.y) {
            return C;
        }
        let vctBC = getVector(B, C);
        let vctCB = getVector(C, B);
        let vctBP = getVector(B, P);
        let rAngle = getRotateAng(vctBC, vctBP);
        rAngle = rAngle > 180 ? rAngle - 360 : rAngle;
        let vctCQ = getRotateVct(vctCB, -rAngle * 0.6);
        let Q = getPntInVct(C, vctCQ, getVctLen(vctBP) * 0.7);
        return Q;
    };

    /**
     * (平滑曲线)优化控制点的作用，使曲线更平滑(以某个点P1为分析点)
     */
    export function optCNodes(cNodes: any) {
        let len = cNodes.length;
        for (let i = 0; i < len - 1; i++) {
            let P1 = cNodes[i].P1,
                P2 = cNodes[i + 1].P1,
                P1A1 = getLenOfTwoPnts(P1, cNodes[i].Ai),
                P2B2 = getLenOfTwoPnts(P2, cNodes[i + 1].Bi),
                P1P2 = getLenOfTwoPnts(P1, P2);

            //相邻角度优化
            if (cNodes.length >= 4) {
                if (i > 0 && i < len - 2) {
                    let P0 = cNodes[i - 1].P1,
                        P3 = cNodes[i + 2].P1,
                        vctP1P0 = getVector(P1, P0),
                        vctP1P2 = getVector(P1, P2),
                        vctP3P2 = getVector(P3, P2),
                        rAngTo;
                    let rAngP1 = getRotateAng(vctP1P0, vctP1P2),
                        rAngP2 = getRotateAng(vctP1P2, vctP3P2);
                    rAngP1 = rAngP1 > 180 ? 360 - rAngP1 : rAngP1;
                    rAngP2 = rAngP2 > 180 ? 360 - rAngP2 : rAngP2;
                    if (rAngP1 < rAngP2) {
                        let vctP1Ai = getVector(P1, cNodes[i].Ai);
                        let rAngP1AiP1P2 = getRotateAng(vctP1Ai, vctP1P2);
                        rAngP1AiP1P2 = rAngP1AiP1P2 > 180 ? rAngP1AiP1P2 - 360 : rAngP1AiP1P2;
                        rAngTo = rAngP1AiP1P2 >= 0 ? (rAngP2 - rAngP1) / 10 : (rAngP1 - rAngP2) / 10;  //旋转角调整幅度
                    } else {
                        let vctP2Bi = getVector(P2, cNodes[i + 1].Bi);
                        let vctP2P1 = getVector(P2, P1);
                        let rAngP2BiP2P1 = getRotateAng(vctP2Bi, vctP2P1);
                        rAngP2BiP2P1 = rAngP2BiP2P1 > 180 ? rAngP2BiP2P1 - 360 : rAngP2BiP2P1;
                        rAngTo = rAngP2BiP2P1 >= 0 ? (rAngP1 - rAngP2) / 10 : (rAngP2 - rAngP1) / 10;  //旋转角调整幅度
                    }
                    let P1_AiTo = getRotatePnt(P1, cNodes[i].Ai, rAngTo);
                    let P1_BiTo = getRotatePnt(P1, cNodes[i].Bi, rAngTo);
                    let P2_BiTo = getRotatePnt(P2, cNodes[i + 1].Bi, rAngTo);
                    let P2_AiTo = getRotatePnt(P2, cNodes[i + 1].Ai, rAngTo);
                    cNodes[i].Ai = P1_AiTo;
                    cNodes[i].Bi = P1_BiTo;
                    cNodes[i + 1].Ai = P2_AiTo;
                    cNodes[i + 1].Bi = P2_BiTo;
                }
            }

            //设置一段曲线两端的控制长度之和不能过长
            let p = (P1A1 + P2B2) / P1P2,
                std = 0.8;
            if (p > std) {
                P1A1 = P1A1 * std / p;
                P2B2 = P2B2 * std / p;
                let Ai1 = getPntInVct(P1, getVector(P1, cNodes[i].Ai), P1A1);
                let Bi1 = getPntInVct(P2, getVector(P2, cNodes[i + 1].Bi), P2B2);
                cNodes[i].Ai = Ai1;
                cNodes[i + 1].Bi = Bi1;
            }
        }
    };
}